﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD.Core {

    /// <summary>
    /// バウンディング ボリューム コンポーネント
    /// </summary>
    /// <remarks>
    /// <img src="../Images/aaa.png"/>
    /// バウンディング ボリュームはコンポーネントの一種で、ノードを囲う領域を定義します。
    /// バウンディング ボリュームを持つノードはカリング、ピッキング、LOD選択などの対象となります。
    /// <see cref="Mesh"/> オブジェクトの持つ頂点座標はレンダリングのみに使用され、これらの走査では使用されません。
    /// バウンディング ボリュームは箱状の <see cref="BoundingBox"/> または球状の <see cref="BoundingSphere"/> から構成されます。
    /// 両者は両方同時に指定可能です。
    /// <para>
    /// <h3>カリングとバウンディング ボリューム</h3>
    /// DDDはレンダリング実行時にバウンディング ボリュームが設定されていて、かつ視錘台から完全に外に出ているノードは描画しません
    /// （シェーダーが実行されません）。これをカリングと言います。
    /// 一般的にはカリングはシステムの高速化のために用いられ、描画結果に差が出ない事が保証されています。
    /// 従ってバウンディング ボリュームは物体より大きく、完全に包み込むように設定されるべきです。
    /// </para>
    /// <para>
    /// <h3>ピッキングとバウンディング ボリューム</h3>
    /// DDDはピッキングと呼ばれる機能を持ち、シーンからある光線上に存在する一番近い物体を選び出す事が可能です。
    /// ピンキングはバウンディング ボリュームが設定されている物体のみが対象となります。
    /// またピッキングの判定はバウンディング ボリュームに対してのみ行い、ポリゴンレベルでの厳密な判定は行いません。
    /// 従ってかなりおおざっぱなピックになります。
    /// <note type="implement">
    /// ポリゴンレベルで判定しなくて良いの？
    /// これで良いのか自信がないが、現状ではこの仕様でいく。
    /// </note>
    /// </para>
    /// <para>
    /// <h3>LOD選択とバウンディング ボリューム</h3>
    /// またバウンディング ボリュームはLOD選択の基準ともなります。
    /// バウンディングボリュームをLOD選択でも使用され、バウンディングボリュームをスクリーンに投影した時のピクセル数が、
    /// LOD選択の基準となります。
    /// LOD選択の詳細については、<see cref="LODSelector"/> クラスの解説を参照してください。
    /// </para>
    /// </remarks>
    /// <seealso cref="BoundingBox"/>
    /// <seealso cref="BoundingSphere"/>
    /// <seealso cref="LODSelector"/>
    public class BoundingVolume : Component {

        #region Field
        BoundingSphere bsph;
        BoundingBox bbox;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 空のバウンディングボリュームを作成します。
        /// 初期値は以下のとおりです。
        /// <list type="bullet">
        ///   <item><see cref="BoundingBox"/> = {0,0},{0,0},{0,0}</item>
        ///   <item><see cref="BoundingSphere"/> = 0, {0,0,0}</item>
        /// </list>
        /// </remarks>
        public void BoudingVolume () {
            this.bsph = new BoundingSphere ();
            this.bbox = new BoundingBox ();
        }
        #endregion

        #region Property
        /// <summary>
        /// バウンディング ボックス
        /// </summary>
        /// <remarks>
        /// このノードを被う箱状のバウンディング ボリュームです。
        /// </remarks>
        /// <seealso cref="BoundingSphere"/>
        public BoundingBox BoundingBox {
            get { return bbox; }
        }

        /// <summary>
        /// バウンディング スフィア
        /// </summary>
        /// <remarks>
        /// このノードを被う球状のバウンディング ボリュームです。
        /// </remarks>
        /// <seealso cref="BoundingBox"/>
        public BoundingSphere BoundingSphere {
            get { return bsph; }
        }

        /// <inheritdoc/>
        public override IEnumerable<Object> References {
            get {
                return base.References;
            }
        }
        #endregion

        #region Method
        /// <summary>
        /// バウンディング スフィアの設定
        /// </summary>
        /// <remarks>
        /// このバウンディングボリュームに球状のバウンディング スフィアを設定します。
        /// 半径 <paramref name="radius"/> = 0 は体積0の有効なバウンディング スフィアです。
        /// </remarks>
        /// <param name="radius">半径</param>
        /// <param name="x">中心のX座標</param>
        /// <param name="y">中心のY座標</param>
        /// <param name="z">中心のZ座標</param>
        /// <exception cref="ArgumentException"><paramref name="radius"/> &lt; 0</exception>
        /// <seealso cref="SetBoundingBox"/>
        public void SetBoundingSphere (float radius, float x, float y, float z) {
            if (radius < 0) {
                throw new ArgumentException ("Radius is invalid");
            }
            this.bsph = new BoundingSphere (radius, x, y, z);
        }

        /// <summary>
        /// バウンディング ボックスの設定
        /// </summary>
        /// <remarks>
        /// このバウンディング ボリュームに箱状のバウンディング ボックスを設定します。
        /// 上限と下限が等しい体積0のボックスは有効なバウンディング ボックスです。
        /// </remarks>
        /// <param name="minX">X軸方向の下限</param>
        /// <param name="maxX">X軸方向の上限</param>
        /// <param name="minY">Y軸方向の下限</param>
        /// <param name="maxY">Y軸方向の上限</param>
        /// <param name="minZ">Z軸方向の下限</param>
        /// <param name="maxZ">Z軸方向の上限</param>
        /// <exception cref="ArgumentException"><paramref name="minX"/> &gt; <paramref name="maxX"/></exception>
        /// <exception cref="ArgumentException"><paramref name="minY"/> &gt; <paramref name="maxY"/></exception>
        /// <exception cref="ArgumentException"><paramref name="minZ"/> &gt; <paramref name="maxZ"/></exception>
        /// <seealso cref="SetBoundingSphere"/>
        public void SetBoundingBox (float minX, float maxX, float minY, float maxY, float minZ, float maxZ) {
            if (minX > maxX) {
                throw new ArgumentException ("(minX, maxX) are invalid");
            }
            if (minY > maxY) {
                throw new ArgumentException ("(minY, maxY) are invalid");
            }
            if (minZ > maxZ) {
                throw new ArgumentException ("(minZ, maxZ) are invalid");
            }

            this.bbox = new BoundingBox (minX, maxX, minY, maxY, minZ, maxZ);
        }

        /// <inheritdoc/>
        public override string ToString () {
            return string.Format ("BoundingVolume: ");
        }

        #endregion

    }
}
